#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <sys/time.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>

#include "definition.h"
//#include "packetprocess.h"

#define SERV_IP "127.0.0.1"
#define SERV_PORT 6339
#define MAXNEVENTS 200
#define BUF_SIZE 1048576

#define MIN(X, Y) (((X) < (Y)) ? (X) : (Y))

void stopmain(int signal);
int server_fd, efd;
char *buff;

int main(int argc, char *argv[]) {
    // ./run [PORT] [IP address] [loop times] [len_msg] [num_msg]
	char helpinfo[] = "Usage: ./epoll_client [-h | -pitln args]\n\n"
					  "Options: \n"
					  "\t-p: server port number, default 6339; \n"
					  "\t-i: ip address, default 127.0.0.1; \n"
					  "\t-t: loop times, default 5; \n"
					  "\t-l: length of msg, default 100; \n"
					  "\t-n: number of msgs per pkt, default 1; \n"
					  "\t-h: print this help info. \n";
	uint16_t serv_port = SERV_PORT; // port
	char *serv_ip = NULL;
	int loop = 5; // here loop is in second
	int len_msg = 100;
	int num_msg = 1;
	int getpidval = getpid();
	int optval;
	while ((optval = getopt(argc, argv, "p:i:t:l:n:h")) != -1) {
		switch (optval) {
		case 'p': // port number
			serv_port = atoi(optarg);
			break;
		case 'i': // ip
			serv_ip = optarg;
			break;
		case 't': // loop times
			loop = atoi(optarg);
			break;
		case 'l': // length of msg
			len_msg = atoi(optarg);
			break;
		case 'n': // number of msgs in a pkt
			num_msg = atoi(optarg);
			break;
		case 'h': // help infomation
			printf("%s", helpinfo);
			exit(-1);
			break;
		case '?': // undefined opt
			break;
		case ':': // no arg
			break;
		default:
			break;
		}
	}

	if (loop <= 0) loop = 5; // you could change the minimal loop(/sec)

    struct sockaddr_in server_addr, client_addr;
    socklen_t len_cli = sizeof(struct sockaddr);

    struct epoll_event events[MAXNEVENTS], tmp_event;

	signal(SIGINT, stopmain);

    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
		printf("%d: socket failed. \n", getpidval); exit(-1);
    }

	bzero(&server_addr, sizeof(struct sockaddr_in));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(serv_port);
	if (serv_ip != NULL) server_addr.sin_addr.s_addr = inet_addr(serv_ip);
    else server_addr.sin_addr.s_addr = inet_addr(SERV_IP);

    if (connect(server_fd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) == -1) {
		printf("%d: connect failed. \n", getpidval); exit(-1);
    }

    fcntl(server_fd, F_SETFL, fcntl(server_fd, F_GETFL, 0) | O_NONBLOCK);

    if ((efd = epoll_create(MAXNEVENTS)) == -1) {
		printf("%d: epoll_create failed. \n", getpidval); exit(-1);
    }

    tmp_event.events = EPOLLOUT | EPOLLET;
    tmp_event.data.fd = server_fd;

    epoll_ctl(efd, EPOLL_CTL_ADD, server_fd, &tmp_event);

    int num_epwait, tmpi, tempfd;

	//memset(buff, 'A', 2048);
    if (len_msg <= 0)
        len_msg = 1;
	if(len_msg > 65536)
		len_msg = 65536;
    //buff[len_msg - 1] = '\0';

    if (num_msg < 1) num_msg = 1;
	buff = (char *)malloc(len_msg + 10);
	int len_all = create_sample_fixpacket(num_msg, buff, len_msg);

    char buffread[BUF_SIZE];

    int read_times = 0, write_times = 0, percent = 1;
    int read_total = 0, write_total = 0;
    int len_send = 0, len_recv = 0;

	struct timeval time_start, time_current;
	gettimeofday(&time_current, NULL);
	gettimeofday(&time_start, NULL);
    time_t time_interval = time_current.tv_sec - time_start.tv_sec;

    //printf("#### begin while loop\n");
	while (time_interval < loop || read_times < write_times) { //
	//while (read_times < loop || write_times < loop) {
        //printf("#### in while loop\n");
        num_epwait = epoll_wait(efd, events, MAXNEVENTS, -1);
        for (tmpi = 0; tmpi < num_epwait; tmpi++) { // for every new event

            if ((events[tmpi].events & EPOLLOUT) && (time_interval < loop)) { // data out
                //printf("EPOLLOUT ----------\n");
                tempfd = events[tmpi].data.fd;
                if (tempfd < 0) {
                    printf("Why sockfd is 0??????????????????????????????\n"); continue;
                }
                int len_out = len_all - len_send;
                int ret;

                while (len_out > 0) {
                                        ret = write(tempfd, buff + len_send, (len_out > 4096) ? 4096 : len_out);
                    if (ret > 0) {
                                                //printf("[send]ret > 0\n"); ////////
                        len_out -= ret; // write
                        len_send += ret;
                    }
                    else if (errno == EAGAIN) { //ret == -1 && 
						printf("%d: [send]Write buffer full.\n", getpidval);
                        break;
                    }
                    else if (errno != EAGAIN) { //ret == -1 && 
						printf("%d: [send]close client: %d. \n", getpidval, tempfd);
                        close(tempfd);
                        goto FINISHSH;
                    }
                }

                //printf("####[send]ret: %d, errno: %d, len out: %d, len send: %d\n", ret, errno, len_out, len_send); /////////////

                if (len_send == len_all) { //finish send
                    tmp_event.events = EPOLLIN | EPOLLET;
                    tmp_event.data.fd = tempfd;
                    epoll_ctl(efd, EPOLL_CTL_MOD, tempfd, &tmp_event);

                    write_total += len_send;
                    len_send = 0;

                    //printf("write: %d times\n", write_times);
                    write_times++;
                }
                else {//buffer full
                    printf("%d bytes to write.\n", len_all - len_send);
                }
            }

            else if (events[tmpi].events & EPOLLIN) { // data in
                //printf("EPOLLIN -----------\n");
                tempfd = events[tmpi].data.fd;
                if (tempfd < 0) {
                    printf("Why sockfd is 0??????????????????????????????\n");
                    continue;
                }
                //*
                int ret, len_in = len_all - len_recv;

                while ((ret = read(tempfd, buffread, MIN(BUF_SIZE, len_in) )) > 0) {
                //while ((ret = read(tempfd, buffread, BUF_SIZE)) > 0) {
                                        //printf("[recv]ret > 0\n"); ////////
                    len_recv += ret;
                    len_in -= ret;
                    if (len_in == 0) {
                        break;
                    }
                }
				//printf("recvdata: %s\n", buffread + sizeofHeader);
                //printf("####[read]ret: %d, errno: %d, ret: %d, recv len: %d\n", ret, errno, ret, len_recv);
                if (ret == -1 && errno == EAGAIN) {  
                    //printf("[recv]Buffer empty.\n");
                }
                else if (ret == 0 && errno != 0) {
					printf("%d: [recv]close client: %d. \n", getpidval, tempfd);
                    close(tempfd);
                    goto FINISHSH;
                }
                else if (ret < 0) {
					printf("%d: [recv]Read error, errno: %d. close client: %d. \n", getpidval, errno, tempfd);
                    close(tempfd);
                    goto FINISHSH;
                }


                if (len_recv == len_all) { //finish recv
                    tmp_event.events = EPOLLOUT | EPOLLET;
                    tmp_event.data.fd = tempfd;
                    epoll_ctl(efd, EPOLL_CTL_MOD, tempfd, &tmp_event);

                    read_total += len_recv;
                    len_recv = 0;

                    //printf("read: %d times\n", read_times);
                    read_times++;
                }
                else {//buffer empty
                    //printf("%d bytes to read.\n", len_all - len_recv);
                }
                //*/
            }
        } // finished for-loop

        //if(write_times * 10 >= percent * loop) {
            //printf("%d0\%\n", percent++);
        //}
        //printf("@@@@ end of while-loop................\n");
        // if (--loop < 0) break;
		gettimeofday(&time_current, NULL);
        time_interval = time_current.tv_sec - time_start.tv_sec;
    } // finished while-loop
	printf("%d: read_total: %d write_total: %d\n", getpidval, read_total, write_total);

FINISHSH:
    close(server_fd);
    close(efd);
    free(buff);
    //printf("#### end while loop, close\n");
    return 0;
}

void stopmain(int signal) {
    printf("**** Ctrl+C ****\n");
    close(server_fd);
    close(efd);
    free(buff);
    printf("**** End Ctrl+C \n");
    _exit(0);
}

